package qiyu.live.gift.provider.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import jakarta.annotation.Resource;
import org.idea.qiyu.live.framework.redis.starter.key.GiftProviderCacheKeyBuilder;
import org.qiyu.live.common.interfaces.enums.CommonStatusEnum;
import org.qiyu.live.common.interfaces.utils.ListUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import qiyu.live.gift.dto.RedPacketConfigReqDTO;
import qiyu.live.gift.dto.RedPacketReceiveDTO;
import qiyu.live.gift.provider.dao.mapper.IRedPacketMapper;
import qiyu.live.gift.provider.dao.po.RedPacketConfigPO;
import qiyu.live.gift.provider.service.IRedPacketConfigService;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: sfx
 * Date: 2023-11-12
 * Time: 16:45
 */
@Service
public class RedPacketConfigServiceImpl implements IRedPacketConfigService {

    private static final Logger LOGGER = LoggerFactory.getLogger(RedPacketConfigServiceImpl.class);

    @Resource
    private IRedPacketMapper redPacketMapper;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private GiftProviderCacheKeyBuilder cacheKeyBuilder;

    @Override
    public RedPacketConfigPO queryRedPacketConfigByAnchorId(Integer anchorId) {
        LambdaQueryWrapper<RedPacketConfigPO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(RedPacketConfigPO::getAnchorId, anchorId);
        queryWrapper.eq(RedPacketConfigPO::getStatus, CommonStatusEnum.VALID.getCode());
        queryWrapper.last("limit 1");
        return redPacketMapper.selectOne(queryWrapper);
    }

    @Override
    public boolean insertOne(RedPacketConfigPO redPacketConfigPO) {
        return redPacketMapper.insert(redPacketConfigPO) > 0;
    }

    @Override
    public boolean updateById(RedPacketConfigPO redPacketConfigPO) {
        LambdaUpdateWrapper<RedPacketConfigPO> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(RedPacketConfigPO::getAnchorId, redPacketConfigPO.getAnchorId());
        return redPacketMapper.update(redPacketConfigPO, updateWrapper) > 0;
    }

    /**
     * 初始化红包雨
     *
     * @param anchorId 主播id
     * @return 返回是否初始化成功
     */
    @Override
    public boolean prepareRedPacket(Integer anchorId) {
        // 1. 根据主播id查找到对应的红包雨配置
        RedPacketConfigPO configPO = this.queryRedPacketConfigByAnchorId(anchorId);
        if (configPO == null) {
            // 1. 主播id配置红包雨的特权
            // 2. 重复开启红包雨特效
            return false;
        }
        // 1.1 进行防重,防止重复请求来 --> 使用分布式锁来解决
        String lockKey = cacheKeyBuilder.buildRedPacketLockCacheKey(configPO.getConfigCode());
        Boolean isSuccess = redisTemplate.opsForValue().
                setIfAbsent(lockKey, 1, 3, TimeUnit.SECONDS);
        if (Boolean.FALSE.equals(isSuccess)) {
            // 二次加锁失败,有重复请求
            return false;
        }
        // 2. 准备红包数据(限制总金额,总个数)
        // 2.1 总金额数
        Integer totalPrice = configPO.getTotalPrice();
        // 2.2 总红包数
        Integer totalCount = configPO.getTotalCount();
        // 2.3 准备红包数据-->使用二倍随机法-->红包随机算法
        List<Integer> amountList = divideRedPackage(totalPrice, totalCount);
        // 3. 保存到redis中
        String cacheKey = cacheKeyBuilder.buildRedPacketListCacheKey(configPO.getConfigCode());
        // 我们这里需要进行优化,如果我们的红包数据非常的多,我们的leftAll命令
        // 是将leftPush命令放到一个缓冲区中,如果一次性放入很多的话,导致缓冲区打满
        // 所以我们并不一次性放入那么多的数据,我们将这个集合进行拆分,拆分成多个小的list
        // 这样我们就可以防止这种情况的发生了
        List<List<Integer>> subLists = ListUtils.subLists(amountList, 100);
        for (List<Integer> subList : subLists) {
            redisTemplate.opsForList().leftPushAll(cacheKey, subList);
        }
        // 4. 更新最终的configPO对象为无效状态
        configPO.setStatus(CommonStatusEnum.NO_VALID.getCode());
        updateById(configPO);

        // 5. 当红包雨初始化完成之后,我们要在redis中记录一下已经初始化完成
        String codeCacheKey = cacheKeyBuilder.buildRedPacketPrepareSuccessCacheKey(configPO.getConfigCode());
        redisTemplate.opsForValue().set(codeCacheKey,1,1,TimeUnit.DAYS);
        return true;
    }

    /**
     * 使用二倍随机均值法生成红包
     *
     * @param totalPrice 红包总金额
     * @param totalCount 红包总数量
     * @return 返回生成的随机数据
     */
    private List<Integer> divideRedPackage(Integer totalPrice, Integer totalCount) {
        List<Integer> res = new ArrayList<>();
        for (int i = 0; i < totalCount; ++i) {
            if (totalCount == i + 1) {
                // 最后一个直接加上
                res.add(totalPrice);
                break;
            }
            int maxLimit = (totalPrice / (totalCount - i)) * 2;
            // 生成的随机金额
            int random = ThreadLocalRandom.current().nextInt(1, maxLimit);
            res.add(random);
            totalPrice -= random;
        }
        return res;
    }

    @Override
    public RedPacketReceiveDTO redPacketReceive(RedPacketConfigReqDTO redPacketConfigReqDTO) {
        String code = redPacketConfigReqDTO.getConfigCode();
        String cacheKey = cacheKeyBuilder.buildRedPacketListCacheKey(code);
        // 1. 获取到红包金额
        Object popRedPacket = redisTemplate.opsForList().rightPop(cacheKey);
        if (popRedPacket == null) {
            // 已经没有红包了,红包领取完毕了
            return null;
        }
        // 2. 记录一下必要的值 如 红包雨结束后 领取红包的总金额, 领取红包的总个数,领取红包的最大金额
        // 这些记录,我们都先在redis中记录,后续等主播下播之后,我们在将缓存中的值同步到数据库
        // 2.1 领取红包的总个数
        String totalGetCacheKey = cacheKeyBuilder.buildRedPacketTotalGetCacheKey(code);
        redisTemplate.opsForValue().increment(totalGetCacheKey, 1);
        redisTemplate.expire(totalGetCacheKey, 1, TimeUnit.DAYS);
        // 2.2 领取红包的总金额
        String totalGetPriceCacheKey = cacheKeyBuilder.buildRedPacketTotalGetPriceCacheKey(code);
        redisTemplate.opsForValue().increment(totalGetPriceCacheKey, (Long) popRedPacket);
        redisTemplate.expire(totalGetPriceCacheKey, 1, TimeUnit.DAYS);

        // 2.3 记录一下用户整场直播领取了多少钱,做一下记录->redis中记录
        String userCacheKey = cacheKeyBuilder.buildRedPacketUserRecordPriceCacheKey(redPacketConfigReqDTO.getUserId());
        redisTemplate.opsForValue().increment(userCacheKey,(Long) popRedPacket);

        // 2.4 领取红包的最大金额
        String maxGetPriceCacheKey = cacheKeyBuilder.buildRedPacketMaxGetPriceCacheKey(code);
        // todo 采用lua脚本记录最大金额
        // 3. 返回给用户领取的红包金额
        LOGGER.info("[redPacketReceive] popRedPacket is {}", popRedPacket);
        return new RedPacketReceiveDTO((Integer) popRedPacket);
    }
}